<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
    <head>
        <title>Binding Test</title>
        <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.4.1.css">
    </head>
    <body>
        <div id="qunit"></div>
        <div id="qunit-fixture"></div>
        <script src="https://code.jquery.com/qunit/qunit-2.4.1.js"></script>

        <!--<script type="text/javascript">
        (async function() {
            // <embed user provided code here>

            await CefSharp.BindObjectAsync("boundAsync", "bound");
        })();

        (async () => {
            await CefSharp.BindObjectAsync("boundAsync", "bound");
        })();
        </script>-->

        <script type="text/javascript">
        (async () =>
        {
            // Verify that two objects are completely equal
            function deepEqual(x, y)
            {
                if ((typeof x == "object" && x != null) && (typeof y == "object" && y != null))
                {
                    for (var prop in x)
                    {
                        if (prop in y && (Object.keys(x).length === Object.keys(y).length))
                        {
                            return deepEqual(x[prop], y[prop]);
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
                else
                {
                    return x === y;
                }
            }

            await CefSharp.BindObjectAsync("boundAsync", "bound");

            QUnit.test( "BindObjectAsync Second call", function( assert )
            {
                let asyncCallback = assert.async();
                CefSharp.BindObjectAsync("boundAsync", "bound").then(function(res)
                {
                    assert.equal(res.Success, false, "Second call to BindObjectAsync with already bound objects as params returned false.");
                    asyncCallback();
                });
            });

            QUnit.test( "bound.repeat('hi ', 5)", function( assert )
            {
                const expectedResult = "hi hi hi hi hi "
                let actualResult = bound.repeat("hi ", 5);
                assert.equal( actualResult, expectedResult, "We expect value to be " + actualResult );
            });

            QUnit.test( "bound.myProperty", function( assert )
            {
                const expectedResult = 42;
                let actualResult = bound.myProperty;

                assert.equal(actualResult, expectedResult, "We expect value to be " + expectedResult);
            });

            QUnit.test( "bound.subObject.simpleProperty", function( assert )
            {
                const expectedResult = "This is a very simple property.";
                let actualResult = bound.subObject.simpleProperty;

                assert.equal(actualResult, expectedResult, "We expect value to be " + expectedResult);

                bound.subObject.simpleProperty = expectedResult + "1";

                actualResult = bound.subObject.simpleProperty;

                assert.equal(actualResult, (expectedResult + "1"), "We expect value to be " + (expectedResult + 1));

                //Reset to default value
                bound.subObject.simpleProperty = expectedResult;
            });

            QUnit.test( "Function delegate to c# method", function( assert )
            {
                function myFunction(functionParam)
                {
                    return functionParam();
                }

                const expectedResult = "42"
                let actualResult = myFunction(bound.echoMyProperty);
                assert.equal( actualResult, expectedResult, "We expect value to be " + actualResult );
            });

            QUnit.test( "Function returning complex type", function( assert )
            {
                function myFunction(functionParam)
                {
                    return functionParam();
                }

                const expectedResult = "This is a very simple property."
                let actualResult = bound.getSubObject().simpleProperty;
                assert.equal( actualResult, expectedResult, "Call to bound.getSubObject().simpleProperty resulted in : " + actualResult );
            });

            QUnit.test("Stress Test", function( assert )
            {
                let stressTestCallCount = 1000;
                for (var i = 1; i <= stressTestCallCount; i++)
                {
                    assert.ok(true, bound.repeat("hi ", 5));
                }

                assert.ok(true,  "Stress Test done with : " + stressTestCallCount + " call to bound.repeat(\"hi \", 5)");
            });

            QUnit.test( "JSON Serializer Test", function( assert )
            {
                var json = bound.returnJsonEmployeeList();
                var jsonObj = JSON.parse(json);

                assert.ok(jsonObj.employees.length > 0, "Employee Count : " + jsonObj.employees.length);

                var employees = jsonObj.employees;

                assert.equal("John Doe", employees[0].firstName + " " + employees[0].lastName, "Employee : " + employees[0].firstName + " " + employees[0].lastName);
                assert.equal("Anna Smith", employees[1].firstName + " " + employees[1].lastName, "Employee : " + employees[1].firstName + " " + employees[1].lastName);
                assert.equal("Peter Jones", employees[2].firstName + " " + employees[2].lastName, "Employee : " + employees[2].firstName + " " + employees[2].lastName);
            });

            QUnit.test( "Javascript function sending variable number of parameters to Bound Object. (ParamArray Test)", function( assert )
            {
                assert.equal(bound.methodWithParams('With 1 Params', 'hello-world'), "Name:With 1 Params;Args:hello-world", "Method with paramArray with one param");
                assert.equal(bound.methodWithParams('With 2 Params', 'hello-world', 'chris was here'), "Name:With 2 Params;Args:hello-world, chris was here", "Method with paramArray with two params");
                assert.equal(bound.methodWithParams('With no Params'), "Name:With no Params;Args:", "Method with paramArary no params passed");
                assert.equal(bound.methodWithoutParams('Normal Method', 'hello'), "Normal Method, hello", "Method with one param (not param array)");
                assert.equal(bound.methodWithoutAnything(), "Method without anything called and returned successfully.", "Method without params");

                assert.equal(bound.methodWithThreeParamsOneOptionalOneArray(null), "MethodWithThreeParamsOneOptionalOneArray:No Name Specified - No Optional Param Specified;Args:", "bound.methodWithThreeParamsOneOptionalOneArray(null)");
                assert.equal(bound.methodWithThreeParamsOneOptionalOneArray(null, null), "MethodWithThreeParamsOneOptionalOneArray:No Name Specified - No Optional Param Specified;Args:", "bound.methodWithThreeParamsOneOptionalOneArray(null, null)");
                assert.equal(bound.methodWithThreeParamsOneOptionalOneArray("Test", null), "MethodWithThreeParamsOneOptionalOneArray:Test - No Optional Param Specified;Args:", "bound.methodWithThreeParamsOneOptionalOneArray('Test', null)");
                assert.equal(bound.methodWithThreeParamsOneOptionalOneArray(null, null, "Arg1", "Arg2"), "MethodWithThreeParamsOneOptionalOneArray:No Name Specified - No Optional Param Specified;Args:Arg1, Arg2", "bound.methodWithThreeParamsOneOptionalOneArray(null, null, 'Arg1', 'Arg2')");
            });

            QUnit.test("Methods and Properties on bound object 'bound':", function( assert )
            {
                for (var name in bound)
                {
                    if (bound[name].constructor.name != 'Function') continue;

                    assert.ok(true, "Property: " + name);
                }

                for (var name in bound)
                {
                    if (bound[name].constructor.name === 'Function') continue;


                    assert.ok(true, "Function: " + name);
                }

                for (var name in bound)
                {
                    if (typeof bound[name] == "object" && bound[name] !== null)
                    {
                        for (var sub in bound[name])
                        {
                            var type  = bound[name][sub].constructor.name === 'Function' ? "Function" : "Property";
                            assert.ok(true, name + "." + sub + "(" + type + ")");
                        }
                    }
                }
            });

            QUnit.test( "Async call (Throw .Net Exception)", function( assert )
            {
                var asyncCallback = assert.async();

                boundAsync.error().catch(function (e)
                {
                    assert.ok( true, "Error: " + e );

                    asyncCallback();
                });
            });

            QUnit.test( "Async call (Divide 16 / 2):", function( assert )
            {
                var asyncCallback = assert.async();

                boundAsync.div(16, 2).then(function (actualResult)
                {
                    const expectedResult = 8

                    assert.equal(expectedResult, actualResult, "Divide 16 / 2 resulted in " + expectedResult);

                    asyncCallback();
                });                
            });

            QUnit.test( "Async call (Divide 16 /0)", function( assert )
            {
                var asyncCallback = assert.async();

                boundAsync.div(16, 0).then(function (res)
                {
                    //Will always throw exception
                },
                function (e)
                {
                    assert.ok(true, "Error: " + e + "(" + Date() + ")");

                    asyncCallback();
                });
            });

            QUnit.test( "Async call (Hello):", function( assert )
            {
                var asyncCallback = assert.async();
                
                boundAsync.hello('CefSharp').then(function (res)
                {
                    assert.equal(res, "Hello CefSharp")

                    asyncCallback();
                });
            });

            QUnit.test("Async call (Long Running Task):", function( assert )
            {
                var asyncCallback = assert.async();
                
                boundAsync.doSomething().then(function (res)
                {
                    assert.ok(true, "Slept for 1000ms")

                    asyncCallback();
                });
            });

            QUnit.test("Async call (return Struct):", function( assert )
            {
                var asyncCallback = assert.async();
                
                boundAsync.returnObject('CefSharp Struct Test').then(function (res)
                {
                    assert.equal(res.Value, "CefSharp Struct Test", "Struct with a single field");

                    asyncCallback();
                });
            });

            QUnit.test("Async call (return Class):", function (assert)
            {
                var asyncCallback = assert.async();

                //Returns a class
                boundAsync.returnClass('CefSharp Class Test').then(function (res)
                {
                    const expectedResult = 'CefSharp Class Test';

                    assert.equal(expectedResult, res.Value, "Class with a single property");

                    asyncCallback();
                });
            });

            QUnit.test("Async call (returnStructArray):", function( assert )
            {
                var asyncCallback = assert.async();

                boundAsync.returnStructArray('CefSharp').then(function (res)
                {
                    assert.equal(res[0].Value, "CefSharpItem1", "Expected Result of CefSharpItem1");
                    assert.equal(res[1].Value, "CefSharpItem2", "Expected Result of CefSharpItem2");

                    asyncCallback();
                });
            });

            QUnit.test("Async call (returnClassesArray):", function( assert )
            {
                var asyncCallback = assert.async();

                boundAsync.returnClassesArray('CefSharp').then(function (res)
                {
                    assert.equal(res[0].Value, "CefSharpItem1", "Expected Result of CefSharpItem1");
                    assert.equal(res[1].Value, "CefSharpItem2", "Expected Result of CefSharpItem2");

                    asyncCallback();
                });
            });

            QUnit.test("Async call (echoArray):", function (assert)
            {
                var asyncCallback = assert.async();

                boundAsync.echoArray(["one", null, "three"]).then(function (res)
                {
                    assert.equal(res.length, 3, "Expected result to be length 3");
                    assert.equal(res[0], "one", "Expected Result of 1st item");
                    assert.equal(res[1], null, "Expected Result of 2nd item");
                    assert.equal(res[2], "three", "Expected Result of 3rd item");

                    asyncCallback();
                });
            });

            QUnit.test("Async call (echoValueTypeArray):", function (assert)
            {
                var asyncCallback = assert.async();

                boundAsync.echoValueTypeArray([1, null, 3]).then(function (res)
                {
                    assert.equal(res.length, 3, "Expected result to be length 3");
                    assert.equal(res[0], 1, "Expected Result of 1st item");
                    assert.equal(res[1], 0, "Expected Result of 2nd item");
                    assert.equal(res[2], 3, "Expected Result of 3rd item");

                    asyncCallback();
                });
            });

            QUnit.test("Async call (echoMultidimensionalArray):", function (assert)
            {
                var asyncCallback = assert.async();

                boundAsync.echoMultidimensionalArray([[1, 2], null, [3, 4]]).then(function (res)
                {
                    assert.equal(res.length, 3, "Expected result to be length 3");
                    assert.equal(res[0][0], 1, "Expected Result of 1st item");
                    assert.equal(res[0][1], 2, "Expected Result of 1st item");
                    assert.equal(res[1], null, "Expected Result of 2nd item");
                    assert.equal(res[2][0], 3, "Expected Result of 3rd item");
                    assert.equal(res[2][1], 4, "Expected Result of 3rd item");

                    asyncCallback();
                });
            });

            QUnit.test("Async call (methodReturnList):", function (assert)
            {
                const list1 = 
                    [
                        "Element 0 - First",
                        "Element 1",
                        "Element 2 - Last"
                    ];

                const list2 =
                    [
                        ["Element 0, 0", "Element 0, 1"],
                        ["Element 1, 0", "Element 1, 1"],
                        ["Element 2, 0", "Element 2, 1"]
                    ];

                var asyncCallback = assert.async();
                Promise.all([
                    boundAsync.methodReturnsList(),
                    boundAsync.methodReturnsListOfLists(),
                ]).then(function (results)
                {
                    assert.ok(deepEqual(results[0], list1), "Call to bound.MethodReturnsList() resulted in : " + JSON.stringify(results[0]));
                    assert.ok(deepEqual(results[1], list2), "Call to bound.MethodReturnsListOfLists() resulted in : " + JSON.stringify(results[1]));
                    asyncCallback();
                });

            });

            QUnit.test("Async call (methodReturnsDictionary):", function (assert)
            {
                const dict1 =
                {
                    "five": 5,
                    "ten": 10
                };
                const dict2 =
                {
                    "onepointfive": 1.5,
                    "five": 5,
                    "ten": "ten",
                    "twotwo": [2, 2]
                };
                const dict3 =
                {
                    "data":
                    {
                        "onepointfive": 1.5,
                        "five": 5,
                        "ten": "ten",
                        "twotwo": [2, 2]
                    }
                };

                var asyncCallback = assert.async();
                Promise.all([
                    boundAsync.methodReturnsDictionary1(),
                    boundAsync.methodReturnsDictionary2(),
                    boundAsync.methodReturnsDictionary3()
                ]).then(function(results)
                {
                    assert.ok( deepEqual(results[0], dict1), "Call to bound.MethodReturnsDictionary1() resulted in : " + JSON.stringify(results[0]) );
                    assert.ok( deepEqual(results[1], dict2), "Call to bound.MethodReturnsDictionary2() resulted in : " + JSON.stringify(results[1]) );
                    assert.ok( deepEqual(results[2], dict3), "Call to bound.MethodReturnsDictionary3() resulted in : " + JSON.stringify(results[2]) );
                    asyncCallback();
                });
            });

            QUnit.test("Javascript Callback Test using object as parameter", function( assert )
            {
                var asyncCallback = assert.async();

                function objectCallback(s)
                {
                    assert.ok(true, s);

                    asyncCallback();
                }

                bound.testCallbackFromObject(
                {
                    Callback: objectCallback, TestString: "Hello", SubClasses:
                    [
                        { PropertyOne: "Test Property One", Numbers: [1, 2, 3] },
                        { PropertyOne: "Test Property Two", Numbers: [4, 5, 6] }
                    ]
                });
            });

            QUnit.test("Javascript Callback Test with DateTime", function( assert )
            {
                var asyncCallback = assert.async();

                function callback(dateTimeFirst, dateTimeSecondAsArray)
                {
                    var arr = dateTimeSecondAsArray;
                    var dateTimeSecond = new Date(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
                    assert.equal(dateTimeFirst - dateTimeSecond, 0);

                    asyncCallback();
                }

                bound.testCallbackWithDateTime(callback);
            });
            
            QUnit.test("Javascript Callback Test with 1900 DateTime", function (assert) {
                var asyncCallback = assert.async();

                function callback(dateTimeFirst, dateTimeSecondAsArray) {
                    var arr = dateTimeSecondAsArray;
                    var dateTimeSecond = new Date(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
                    assert.equal(dateTimeFirst - dateTimeSecond, 0);

                    asyncCallback();
                }

                bound.testCallbackWithDateTime1900(callback);
            });

            QUnit.test("Javascript Callback Test with 1970 DateTime", function (assert) {
                var asyncCallback = assert.async();

                function callback(dateTimeFirst, dateTimeSecondAsArray) {
                    var arr = dateTimeSecondAsArray;
                    var dateTimeSecond = new Date(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
                    assert.equal(dateTimeFirst - dateTimeSecond, 0);

                    asyncCallback();
                }

                bound.testCallbackWithDateTime1970(callback);
            });

            QUnit.test("Javascript Callback Test with 1985 DateTime", function (assert) {
                var asyncCallback = assert.async();

                function callback(dateTimeFirst, dateTimeSecondAsArray) {
                    var arr = dateTimeSecondAsArray;
                    var dateTimeSecond = new Date(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
                    assert.equal(dateTimeFirst - dateTimeSecond, 0);

                    asyncCallback();
                }

                bound.testCallbackWithDateTime1985(callback);
            });

            QUnit.test("Javascript Callback Test", function( assert )
            {
                var asyncCallback = assert.async();

                function callback(s)
                {
                    assert.equal(s.Response, "This callback from C# was delayed 1500ms", s.Response);

                    asyncCallback();
                }

                bound.testCallback(callback);
            });

            //TODO: Unable to map this to a qunit test
            //QUnit.test("Disposed Javscript Callback Test (navigates to www.google.com before callback fires)", function( assert )
            //{
            //    var asyncCallback = assert.async();

            //    function disposedCallback(s)
            //    {
            //        // This callback should be disposed and should not be called
            //        assert.ok(false, "This callback should not have been called");

            //        asyncCallback();
            //    }

            //    bound.testCallback(callback);

            //    window.location.assign("http://www.google.com");
            //});
        })();
        </script>

        <script type="text/javascript">
            QUnit.test( "Bind boundAsync2 and call (Hello):", function( assert )
            {
                var asyncCallback = assert.async();

                //Can use both cefSharp.bindObjectAsync and CefSharp.BindObjectAsync, both do the same
                cefSharp.bindObjectAsync({ NotifyIfAlreadyBound: true }, "boundAsync2").then(function (result)
                {
                    boundAsync2.hello('CefSharp').then(function (res)
                    {
                        assert.equal(res, "Hello CefSharp")

                        assert.equal(true, CefSharp.DeleteBoundObject("boundAsync2"), "Object was unbound");

                        assert.ok(window.boundAsync2 === undefined, "boundAsync2 is now undefined");

                        asyncCallback();
                    });
                });
            });

            //Repeat of the previous test to make sure binding a deleted object is working
            QUnit.test("Bind boundAsync2 and call (Hello) Second Attempt:", function (assert)
            {
                var asyncCallback = assert.async();

                CefSharp.BindObjectAsync({ NotifyIfAlreadyBound: true, IgnoreCache: true }, "boundAsync2").then(function (result)
                {
                    boundAsync2.hello('CefSharp').then(function (res)
                    {
                        assert.equal(res, "Hello CefSharp")

                        asyncCallback();
                    });
                });
            });

            //Repeat of the previous test to make sure binding a deleted object is working
            QUnit.test( "Bind boundAsync2 and call (Hello) Third Attempt:", function( assert )
            {
                var asyncCallback = assert.async();

                CefSharp.BindObjectAsync({ NotifyIfAlreadyBound : true, IgnoreCache : true }, "boundAsync2").then(function(result)
                {
                    boundAsync2.hello('CefSharp').then(function (res)
                    {
                        assert.equal(res, "Hello CefSharp")

                        assert.equal(true, CefSharp.DeleteBoundObject("boundAsync2"), "Object was unbound");

                        assert.ok(window.boundAsync2 === undefined, "boundAsync2 is now undefined");

                        asyncCallback();
                    });
                });
            });
        </script>

    </body>
</html>
